//原始写法一：
function Fn(a,b) {
	this.a=a;
	this.b=b;
	this.fn=function() {
		console.log(this.a+this.b)
	}
}
var obj=new Fn(8,4); 
obj.fn(); //12
console.log(new Fn(1,2).b) //2
console.log(obj.b)  //4
console.log(new Fn().a)  //undefined
console.log(new Fn().fn)  //[Function]

console.log('-----------------------------')

//原始写法二：
//Num是一个构造函数，而Num.prototype是其的原型对象
//实际上Num相当于一个未执行的对象(未加入内存),new出来之后才能使用
//Num.prototype就是一个对象，如var Num={a:'a',b:'b',add:function(){...}}
function Num(a,b) {
	this.a=a;
	this.b=b;
}
Num.prototype.add=function() {
//	return '和为：'+(this.a+this.b)
	return `和为：${this.a+this.b}`   //模板字符串
}
var num = new Num(1,2)
console.log(num.a,num.b,num.add())

console.log('-----------------------------')

//原始写法三：
function Aaa(a,b) {
	this.a=a;
	this.b=b;
}
//assign-分配:Object.assign()为对象合并方法,可以将多个对象合并
//console.log(Object.assign({a:1,b:2},{c:3,d:4})); //{a: 1, b: 2, c: 3, d: 4}
Object.assign(Aaa.prototype,{
	add(){
		return `和为：${this.a+this.b}`
	},
	cha(){
		return `差为：${this.a-this.b}`
	}
})
var aaa = new Aaa(1,2)
console.log(aaa.a,aaa.b,aaa.add(),aaa.cha())

console.log('-----------------------------')

//es6写法：
// 注意：类和模块的内部，默认就是严格模式，所以不需要使用use strict指定运行模式
// 类不存在变量提升（hoist），这一点与 ES5 完全不同。
let str = 'fn2',str2 = '0'
class ABC{
	constructor(a,b,c){ //这个函数是构造函数，只要执行new就会自动执行
		//这里的this指代整体Obj类
		this.a=a;
		this.b=b;
		this.c=c;
	}
	fn(){
		console.log(this.a+this.b)
	}
	[str](){ //调用变量名为其方法
		return '我爱你'
	}
	/* 类的属性名，可以采用表达式。 */
	[str+str2](){ //调用变量名为其方法
		return '我爱你2'
	}
}
let abc=new ABC(3,4,1)
abc.fn(); //7
console.log(abc.c); //1
console.log(abc.fn2());
console.log(abc.fn20());
console.log(abc[str]());  //调用变量名为其方法
console.log(abc[str+str2]());  //调用变量名为其方法

/* 实例属性除了定义在constructor()方法里面的this上面，也可以定义在类的最外层。 */
// 这样写的好处是如果constructor中只定义属性时，可以省略constructor
// 而且不需要用this关键字
class IncreasingCounter {
  _count = 0;
  get value() {
    console.log('Getting the current value!');
    return this._count;
  }
  increment() {
    this._count++;
  }
}
class foo {
  bar = 'hello';
  baz = 'world';

  constructor() {
    // ...
  }
}


//将类结构为普通函数（注意矫正this的指向）
class Person{
	constructor(){
		this.name = '张三'
	}
	fn(){
		return `我的名字叫${this.name}`
	}
}
let p = new Person()
let {fn} = p
//console.log(fn());  //报错，提示name和fn未定义（this指向变了）
//如下为正确写法：在构造器中绑定this矫正其指向（bind()方法是ES5的内容，在react中使用较多）
class Person2{
	constructor(){
		this.name = '张三'
		this.fn2 = this.fn2.bind(this)  
	}
	fn2(){
		return `我的名字叫${this.name}`
	}
}
// 或者
class Person2{
	name = '张三'
	fn2 = this.fn2.bind(this)  
	fn2(){
		return `我的名字叫${this.name}`
	}
}
let p2 = new Person2()
let {fn2} = p2
console.log(fn2());  //我的名字叫张三

//class的静态方法（不使用new执行类都能够调用的）
class Person3{
	constructor(){
	}
	fn1(){
		console.log(111)
	}
}
//Person3.fn1(); //报错，必须用new对象去执行(Person3.fn1 is not a function)
//如下方法可以直接执行
class Person4{
	constructor(){
	}
	static fn1(){
		console.log(111)
	}
}
Person4.fn1();  //OK

//静态方法可以被子类继承（父子类）
//原生构造对象需要用到call()或者apply()方法去继承
class Person5{
	constructor(name){
		this.name = name
	}
	showName(){
		return `我的名字叫:${this.name}`
	}
}
class Student extends Person5{
	
}
console.log(new Student('张三').showName())

// 注意：父类的静态方法，可以被子类继承。
// 在子类内部，父类的静态方法可以从super对象上调用。
//创建Singleton类。用静态方法实现单例模式。
class Singleton {
    constructor(name) {
        this.name = name;
        this.instance = null;
    }
    // 构造一个广为人知的接口，供用户对该类进行实例化
    static getInstance(name) {
        if(!this.instance) {
            this.instance = new Singleton(name);
        }
        return this.instance;
    }
}
//调用
const singleton = Singleton.getInstance("zhangsan")
// 子类重写父类的静态方法
class Foo {
  static classMethod() {
    return 'hello';
  }
}
class Bar extends Foo {
  static classMethod() {
    return super.classMethod() + ', too';
  }
}
Bar.classMethod() // "hello, too"

/* class的静态属性 */
class MyClass {
  static myStaticProp = 42;

  constructor() {
    console.log(MyClass.myStaticProp); // 42
  }
}


//注意：子类的构造函数如果直接用的话会把父类的覆盖导致不可用，如下直接写会报错
/*class Student extends Person5{
	constructor(){
		
	}
}*/
//子类的构造器中必须使用super()函数，super相当于是父类的constructor
//使用super()相当于把父类的构造器重新执行了一遍，这样就可以避免被覆盖
//但super()里必须要接收参数，如果不接收的话相当于把父类构造器清空
/*class Student2 extends Person6{
	constructor(){
		super()
	}
}*/
//console.log(new Student2('张三').showName())
//如上运行的话会提示showName不存在未undefind
class Person6{
	constructor(name){
		this.name = name
	}
	showName(){
		return `我的名字叫:${this.name}`
	}
}
class Student2 extends Person6{
	constructor(...arg){
		super(arg)
		this.age = arg[1]
	}
	showName(){  //子类的某个方法与父类相同的话会覆盖父类
		//super可以先执行父类的方法
		return super.showName()+`///我的年龄是:${this.age}`
	}
}
console.log(new Student2('张三').showName());  //正确输出'我的名字叫:张三'
console.log(new Student2('张三',12).age);  //正确输出12
console.log(new Student2('张三',12).showName());  //我的年龄是:12



/* class的get和set关键字 */
// 可以监听属性的读写
class MyClass {
	constructor() {
		// ...
	}
	get prop() {
		return 'getter';
	}
	set prop(value) {
		console.log('setter: ' + value);
	}
}
let inst = new MyClass();
inst.prop = 123; // setter: 123
//inst.prop // 'getter'

/* class可以使用表达式定义 */
const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};
// 若在类中没有用到Me，可以省略
const MyClass = class {
  getClassName() {
    return Me.name;
  }
};
// 采用 Class 表达式，可以写出立即执行的 Class。
let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('张三');
person.sayName(); // "张三"


/* class的私有属性和私有方法 */
// 使用#开头，而非private
// 注意：私有属性和方法必须在封闭类中申明，在类的外部不可使用
// 私有属性也可以设置 getter 和 setter 方法。
// 私有属性和私有方法前面，也可以加上static关键字，表示这是一个静态的私有属性或私有方法。
class IncreasingCounter {
	#count = 0;
	get value() {
		console.log('Getting the current value!');
		return this.#count;
	}
	increment() {
		this.#count++;
	}
	
	#fn(){
	  console.log(444)
	}
	
	get #x() { return #count; }
	
	set #x(value) {
	  this.#count = value;
	}
	
	static #totallyRandomNumber = 4;
	
	static #computeRandomNumber() {
		return FakeMath.#totallyRandomNumber;
	}
}
const counter = new IncreasingCounter();
counter.#count // 报错 Private field '#count' must be declared（申明） in an enclosing（封闭） class
counter.#count = 42 // 报错 
counter.#fn() // 报错

/* new.target */
// new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性，该属性一般用在构造函数之中，返回new命令作用于的那个构造函数。
// 如果构造函数不是通过new命令或Reflect.construct()调用的，new.target会返回undefined
// 因此这个属性可以用来确定构造函数是怎么调用的。
// 子类继承父类时，new.target会返回子类。
function Person(name) {
  if (new.target !== undefined) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

// 另一种写法
function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错

// 利用这个特点，可以写出不能独立使用、必须继承后才能使用的类。
class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error('本类不能实例化');
    }
  }
}

class Rectangle extends Shape {
  constructor(length, width) {
    super();
    // ...
  }
}

var x = new Shape();  // 报错
var y = new Rectangle(3, 4);  // 正确